/**
 * 
 */
package ch.comtools.ssh;

import java.io.IOException;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

import ch.comtools.ssh.transport.TransportManager;
import ch.comtools.ssh.transport.impl.DefaultTransportManager;

/**
 * SSH Client.
 * <p>
 * A client holds a {@link SSHConnection} to an SSH server, which makes use of
 * {@link SocketChannel} class (new I/O).
 * </p>
 * @author Roger Dudler <roger.dudler@gmail.com>
 * @since 1.0
 * @version $Id$
 */
public class SSHClient {

	/** Client version. */
	public static final String VERSION = "0.1";
	
	/** Client version identifier. */
	public static final String SOFTWAREVERSION = "comtools-ssh-" + VERSION;
	
	/** Client SSH version. */
	public static final String PROTOCOL_VERSION = "2.0";
	
	/** 
	 * Client identifier string.
	 * <p>
	 * <code>SSH-protoversion-softwareversion SP comments CR LF</code>
	 * </p><p>
	 * as specified in RFC4253#Section-4.2. 
	 * </p><p>
	 * The 'softwareversion' string is
     * primarily used to trigger compatibility extensions and to indicate
     * the capabilities of an implementation.
     * </p><p>
     * The 'comments' string SHOULD
     * contain additional information that might be useful in solving user
     * problems.
     * </p>
	 */
	public static final String ID = "SSH-" + PROTOCOL_VERSION + "-" + SOFTWAREVERSION + " comments";
	
	/** Host name of the ssh server. */
	private String host = "localhost";
	
	/** Port to connect to. */
	private int port = SSH.DEFAULT_PORT;
	
	/** Holds socket connection. */
	private SSHConnection connection;
	
	/** Transport manager. */
	private TransportManager transportManager;
	
	/** Client configuration. */
	private SSHClientConfiguration configuration;
	
	/** Client state. */
	private SSHClientState state;
	
	private Charset charset = Charset.forName("ISO-8859-1");
    private CharsetDecoder decoder = charset.newDecoder();
	
	/**
	 * Creates a new SSH client.
	 */
	public SSHClient() {
		this.state = SSHClientState.INSTANTIATED;
		this.init();
	}
	
	/**
	 * Creates a new SSH client.
	 * @param host
	 * @param port
	 */
	public SSHClient(String host, int port) {
		this.state = SSHClientState.INSTANTIATED;
		this.setHost(host);
		this.setPort(port);
		this.init();
	}
	
	public void init() {
		this.setTransportManager(new DefaultTransportManager(this));
	}
	
	/**
	 * Connect to the configured SSH server.
	 * @throws SSHClientException
	 */
	public void connect() throws SSHClientException {
		
		// 1. create connection
		this.connection = new SSHConnection(this.getHost(), this.getPort());
		try {
			this.connection.connect();
			this.state = SSHClientState.CONNECTED;
		} catch (IOException e) {
			throw new SSHClientException("Connection failed", e);
		}
		
		// 2. protocol version exchange
		SSHProtocolVersionExchange protocolVersionExchange = new SSHProtocolVersionExchange(this);
		try {
			protocolVersionExchange.perform();
		} catch (IOException e) {
			throw new SSHClientException("Protocol version exchange failed", e);
		}
		
		// 3. key exchange
		SSHKeyExchange exchange = new SSHKeyExchange(this);
		try {
			exchange.perform();
		} catch (IOException e) {
			throw new SSHClientException("Key exchange failed", e);
		}
		
	}
	
	/**
	 * @param type
	 * @return
	 */
	public SSHChannel openChannel(String type) throws SSHClientException {
		SSHChannel channel = new SSHChannel(this, type);
		try {
			channel.open();
		} catch (IOException e) {
			throw new SSHClientException("Channel could not be opened", e);
		}
		return channel;
	}
	
	/**
	 * Close SSH client. Closes the underlying socket channel
	 * connection.
	 */
	public void close() {
		try {
			this.connection.close();
			this.state = SSHClientState.CLOSED;
		} catch (IOException e) {
			throw new SSHClientException("Cannot close connection.", e);
		}
	}
	
	/**
	 * @return
	 */
	public SSHConnection getConnection() {
		return connection;
	}
	
	/**
	 * @param host
	 */
	public void setHost(String host) {
		if (this.getState() != SSHClientState.INSTANTIATED) {
			throw new SSHClientException("Cannot change host in current state", new IllegalStateException());
		}
		this.host = host;
	}
	
	/**
	 * @param port
	 */
	public void setPort(int port) {
		if (this.getState() != SSHClientState.INSTANTIATED) {
			throw new SSHClientException("Cannot change port in current state", new IllegalStateException());
		}
		this.port = port;
	}
	
	/**
	 * @return
	 */
	public String getHost() {
		return host;
	}
	
	/**
	 * @return
	 */
	public int getPort() {
		return port;
	}

	/**
	 * @return
	 */
	public TransportManager getTransportManager() {
		return transportManager;
	}

	/**
	 * @param transportManager
	 */
	public void setTransportManager(DefaultTransportManager transportManager) {
		this.transportManager = transportManager;
	}
	
	/**
	 * @return
	 */
	public SSHClientConfiguration getConfiguration() {
		return configuration;
	}

	/**
	 * @param configuration
	 */
	public void setConfiguration(SSHClientConfiguration configuration) {
		if (this.getState() != SSHClientState.INSTANTIATED) {
			throw new SSHClientException("Cannot change configuration in current state", new IllegalStateException());
		}
		this.configuration = configuration;
	}
	
	/**
	 * @return
	 */
	public SSHClientState getState() {
		return state;
	}
	
	/**
	 * @return
	 */
	public CharsetDecoder getDecoder() {
		return decoder;
	}
	
}
